/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"jsapi.h"#include"jsfriendapi.h"#include"js/GCAPI.h"#include"nsString.h"#include"nsTHashtable.h"#include"nsHashKeys.h"#include"nsBaseHashtable.h"#include"nsClassHashtable.h"#include"nsITelemetry.h"#include"mozilla/dom/ToJSValue.h"#include"mozilla/gfx/GPUProcessManager.h"#include"mozilla/Atomics.h"#include"mozilla/StartupTimeline.h"#include"mozilla/StaticMutex.h"#include"mozilla/Unused.h"#include"TelemetryCommon.h"#include"TelemetryHistogram.h"#include"ipc/TelemetryIPCAccumulator.h"#include"base/histogram.h"usingbase::Histogram;usingbase::StatisticsRecorder;usingbase::BooleanHistogram;usingbase::CountHistogram;usingbase::FlagHistogram;usingbase::LinearHistogram;usingmozilla::StaticMutex;usingmozilla::StaticMutexAutoLock;usingmozilla::Telemetry::Accumulation;usingmozilla::Telemetry::KeyedAccumulation;usingmozilla::Telemetry::ProcessID;usingmozilla::Telemetry::Common::LogToBrowserConsole;usingmozilla::Telemetry::Common::RecordedProcessType;namespaceTelemetryIPCAccumulator=mozilla::TelemetryIPCAccumulator;//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Naming: there are two kinds of functions in this file://// * Functions named internal_*: these can only be reached via an// interface function (TelemetryHistogram::*). They mostly expect// the interface function to have acquired// |gTelemetryHistogramMutex|, so they do not have to be// thread-safe. However, those internal_* functions that are// reachable from internal_WrapAndReturnHistogram and// internal_WrapAndReturnKeyedHistogram can sometimes be called// without |gTelemetryHistogramMutex|, and so might be racey.//// * Functions named TelemetryHistogram::*. This is the external interface.// Entries and exits to these functions are serialised using// |gTelemetryHistogramMutex|, except for GetKeyedHistogramSnapshots and// CreateHistogramSnapshots.//// Avoiding races and deadlocks://// All functions in the external interface (TelemetryHistogram::*) are// serialised using the mutex |gTelemetryHistogramMutex|. This means// that the external interface is thread-safe, and many of the// internal_* functions can ignore thread safety. But it also brings// a danger of deadlock if any function in the external interface can// get back to that interface. That is, we will deadlock on any call// chain like this//// TelemetryHistogram::* -> .. any functions .. -> TelemetryHistogram::*//// To reduce the danger of that happening, observe the following rules://// * No function in TelemetryHistogram::* may directly call, nor take the// address of, any other function in TelemetryHistogram::*.//// * No internal function internal_* may call, nor take the address// of, any function in TelemetryHistogram::*.//// internal_WrapAndReturnHistogram and// internal_WrapAndReturnKeyedHistogram are not protected by// |gTelemetryHistogramMutex| because they make calls to the JS// engine, but that can in turn call back to Telemetry and hence back// to a TelemetryHistogram:: function, in order to report GC and other// statistics. This would lead to deadlock due to attempted double// acquisition of |gTelemetryHistogramMutex|, if the internal_* functions// were required to be protected by |gTelemetryHistogramMutex|. To// break that cycle, we relax that requirement. Unfortunately this// means that this file is not guaranteed race-free.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE TYPES#define EXPIRED_ID "__expired__"#define SUBSESSION_HISTOGRAM_PREFIX "sub#"#define KEYED_HISTOGRAM_NAME_SEPARATOR "#"#define CONTENT_HISTOGRAM_SUFFIX "#content"#define GPU_HISTOGRAM_SUFFIX "#gpu"#define EXTENSION_HISTOGRAM_SUFFIX "#extension"namespace{usingmozilla::Telemetry::Common::AutoHashtable;usingmozilla::Telemetry::Common::IsExpiredVersion;usingmozilla::Telemetry::Common::CanRecordDataset;usingmozilla::Telemetry::Common::IsInDataset;classKeyedHistogram;typedefnsBaseHashtableET<nsDepCharHashKey,mozilla::Telemetry::HistogramID>CharPtrEntryType;typedefAutoHashtable<CharPtrEntryType>HistogramMapType;typedefnsClassHashtable<nsCStringHashKey,KeyedHistogram>KeyedHistogramMapType;// Hardcoded probesstructHistogramInfo{uint32_tmin;uint32_tmax;uint32_tbucketCount;uint32_thistogramType;uint32_tid_offset;uint32_texpiration_offset;uint32_tdataset;uint32_tlabel_index;uint32_tlabel_count;RecordedProcessTyperecord_in_processes;boolkeyed;constchar*id()const;constchar*expiration()const;nsresultlabel_id(constchar*label,uint32_t*labelId)const;};enumreflectStatus{REFLECT_OK,REFLECT_CORRUPT,REFLECT_FAILURE};typedefStatisticsRecorder::Histograms::iteratorHistogramIterator;}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE STATE, SHARED BY ALL THREADSnamespace{// Set to true once this global state has been initializedboolgInitDone=false;boolgCanRecordBase=false;boolgCanRecordExtended=false;HistogramMapTypegHistogramMap(mozilla::Telemetry::HistogramCount);KeyedHistogramMapTypegKeyedHistograms;boolgCorruptHistograms[mozilla::Telemetry::HistogramCount];// This is for gHistograms, gHistogramStringTable#include"TelemetryHistogramData.inc"// The singleton StatisticsRecorder object for this process.base::StatisticsRecorder*gStatisticsRecorder=nullptr;}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE CONSTANTSnamespace{// List of histogram IDs which should have recording disabled initially.constmozilla::Telemetry::HistogramIDkRecordingInitiallyDisabledIDs[]={mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,// The array must not be empty. Leave these item here.mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD,mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD};}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: Misc small helpersnamespace{boolinternal_CanRecordBase(){returngCanRecordBase;}boolinternal_CanRecordExtended(){returngCanRecordExtended;}boolinternal_IsHistogramEnumId(mozilla::Telemetry::HistogramIDaID){static_assert(((mozilla::Telemetry::HistogramID)-1>0),"ID should be unsigned.");returnaID<mozilla::Telemetry::HistogramCount;}// Note: this is completely unrelated to mozilla::IsEmpty.boolinternal_IsEmpty(constHistogram*h){Histogram::SampleSetss;h->SnapshotSample(&ss);returnss.counts(0)==0&&ss.sum()==0;}boolinternal_IsExpired(constHistogram*histogram){returnhistogram->histogram_name()==EXPIRED_ID;}nsresultinternal_GetRegisteredHistogramIds(boolkeyed,uint32_tdataset,uint32_t*aCount,char***aHistograms){nsTArray<char*>collection;for(constauto&h:gHistograms){if(IsExpiredVersion(h.expiration())||h.keyed!=keyed||!IsInDataset(h.dataset,dataset)){continue;}constchar*id=h.id();constsize_tlen=strlen(id);collection.AppendElement(static_cast<char*>(nsMemory::Clone(id,len+1)));}constsize_tbytes=collection.Length()*sizeof(char*);char**histograms=static_cast<char**>(moz_xmalloc(bytes));memcpy(histograms,collection.Elements(),bytes);*aHistograms=histograms;*aCount=collection.Length();returnNS_OK;}constchar*HistogramInfo::id()const{return&gHistogramStringTable[this->id_offset];}constchar*HistogramInfo::expiration()const{return&gHistogramStringTable[this->expiration_offset];}nsresultHistogramInfo::label_id(constchar*label,uint32_t*labelId)const{MOZ_ASSERT(label);MOZ_ASSERT(this->histogramType==nsITelemetry::HISTOGRAM_CATEGORICAL);if(this->histogramType!=nsITelemetry::HISTOGRAM_CATEGORICAL){returnNS_ERROR_FAILURE;}for(uint32_ti=0;i<this->label_count;++i){// gHistogramLabelTable contains the indices of the label strings in the// gHistogramStringTable.// They are stored in-order and consecutively, from the offset label_index// to (label_index + label_count).uint32_tstring_offset=gHistogramLabelTable[this->label_index+i];constchar*conststr=&gHistogramStringTable[string_offset];if(::strcmp(label,str)==0){*labelId=i;returnNS_OK;}}returnNS_ERROR_FAILURE;}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: Histogram Get, Add, Clone, Clear functionsnamespace{nsresultinternal_CheckHistogramArguments(uint32_thistogramType,uint32_tmin,uint32_tmax,uint32_tbucketCount,boolhaveOptArgs){if(histogramType!=nsITelemetry::HISTOGRAM_BOOLEAN&&histogramType!=nsITelemetry::HISTOGRAM_FLAG&&histogramType!=nsITelemetry::HISTOGRAM_COUNT){// The min, max & bucketCount arguments are not optional for this type.if(!haveOptArgs)returnNS_ERROR_ILLEGAL_VALUE;// Sanity checks for histogram parameters.if(min>=max)returnNS_ERROR_ILLEGAL_VALUE;if(bucketCount<=2)returnNS_ERROR_ILLEGAL_VALUE;if(min<1)returnNS_ERROR_ILLEGAL_VALUE;}returnNS_OK;}/* * min, max & bucketCount are optional for boolean, flag & count histograms. * haveOptArgs has to be set if the caller provides them. */nsresultinternal_HistogramGet(constchar*name,constchar*expiration,uint32_thistogramType,uint32_tmin,uint32_tmax,uint32_tbucketCount,boolhaveOptArgs,Histogram**result){nsresultrv=internal_CheckHistogramArguments(histogramType,min,max,bucketCount,haveOptArgs);if(NS_FAILED(rv)){returnrv;}if(IsExpiredVersion(expiration)){name=EXPIRED_ID;min=1;max=2;bucketCount=3;histogramType=nsITelemetry::HISTOGRAM_LINEAR;}switch(histogramType){casensITelemetry::HISTOGRAM_EXPONENTIAL:*result=Histogram::FactoryGet(name,min,max,bucketCount,Histogram::kUmaTargetedHistogramFlag);break;casensITelemetry::HISTOGRAM_LINEAR:casensITelemetry::HISTOGRAM_CATEGORICAL:*result=LinearHistogram::FactoryGet(name,min,max,bucketCount,Histogram::kUmaTargetedHistogramFlag);break;casensITelemetry::HISTOGRAM_BOOLEAN:*result=BooleanHistogram::FactoryGet(name,Histogram::kUmaTargetedHistogramFlag);break;casensITelemetry::HISTOGRAM_FLAG:*result=FlagHistogram::FactoryGet(name,Histogram::kUmaTargetedHistogramFlag);break;casensITelemetry::HISTOGRAM_COUNT:*result=CountHistogram::FactoryGet(name,Histogram::kUmaTargetedHistogramFlag);break;default:NS_ASSERTION(false,"Invalid histogram type");returnNS_ERROR_INVALID_ARG;}returnNS_OK;}// Read the process type from the given histogram name. The process type, if// one exists, is embedded in a suffix.mozilla::Telemetry::ProcessIDGetProcessFromName(constnsACString&aString){if(StringEndsWith(aString,NS_LITERAL_CSTRING(CONTENT_HISTOGRAM_SUFFIX))){returnProcessID::Content;}if(StringEndsWith(aString,NS_LITERAL_CSTRING(GPU_HISTOGRAM_SUFFIX))){returnProcessID::Gpu;}if(StringEndsWith(aString,NS_LITERAL_CSTRING(EXTENSION_HISTOGRAM_SUFFIX))){returnProcessID::Extension;}returnProcessID::Parent;}constchar*SuffixForProcessType(mozilla::Telemetry::ProcessIDaProcessType){switch(aProcessType){caseProcessID::Parent:returnnullptr;caseProcessID::Content:returnCONTENT_HISTOGRAM_SUFFIX;caseProcessID::Gpu:returnGPU_HISTOGRAM_SUFFIX;caseProcessID::Extension:returnEXTENSION_HISTOGRAM_SUFFIX;default:MOZ_ASSERT_UNREACHABLE("unknown process type");returnnullptr;}}CharPtrEntryType*internal_GetHistogramMapEntry(constchar*aName){nsDependentCStringname(aName);ProcessIDprocess=GetProcessFromName(name);constchar*suffix=SuffixForProcessType(process);if(!suffix){returngHistogramMap.GetEntry(aName);}autoroot=Substring(name,0,name.Length()-strlen(suffix));returngHistogramMap.GetEntry(PromiseFlatCString(root).get());}nsresultinternal_GetHistogramEnumId(constchar*name,mozilla::Telemetry::HistogramID*id){if(!gInitDone){returnNS_ERROR_FAILURE;}CharPtrEntryType*entry=internal_GetHistogramMapEntry(name);if(!entry){returnNS_ERROR_INVALID_ARG;}*id=entry->mData;returnNS_OK;}// O(1) histogram lookup by numeric idnsresultinternal_GetHistogramByEnumId(mozilla::Telemetry::HistogramIDid,Histogram**ret,ProcessIDaProcessType){staticHistogram*knownHistograms[mozilla::Telemetry::HistogramCount]={0};staticHistogram*knownContentHistograms[mozilla::Telemetry::HistogramCount]={0};staticHistogram*knownGPUHistograms[mozilla::Telemetry::HistogramCount]={0};staticHistogram*knownExtensionHistograms[mozilla::Telemetry::HistogramCount]={0};Histogram**knownList=nullptr;switch(aProcessType){caseProcessID::Parent:knownList=knownHistograms;break;caseProcessID::Content:knownList=knownContentHistograms;break;caseProcessID::Gpu:knownList=knownGPUHistograms;break;caseProcessID::Extension:knownList=knownExtensionHistograms;break;default:MOZ_ASSERT_UNREACHABLE("unknown process type");returnNS_ERROR_FAILURE;}Histogram*h=knownList[id];if(h){*ret=h;returnNS_OK;}constHistogramInfo&p=gHistograms[id];if(p.keyed){returnNS_ERROR_FAILURE;}nsAutoCStringhistogramName;histogramName.Append(p.id());if(constchar*suffix=SuffixForProcessType(aProcessType)){histogramName.AppendASCII(suffix);}nsresultrv=internal_HistogramGet(histogramName.get(),p.expiration(),p.histogramType,p.min,p.max,p.bucketCount,true,&h);if(NS_FAILED(rv))returnrv;#ifdef DEBUG// Check that the C++ Histogram code computes the same ranges as the// Python histogram code.if(!IsExpiredVersion(p.expiration())){conststructbounds&b=gBucketLowerBoundIndex[id];if(b.length!=0){MOZ_ASSERT(size_t(b.length)==h->bucket_count(),"C++/Python bucket # mismatch");for(inti=0;i<b.length;++i){MOZ_ASSERT(gBucketLowerBounds[b.offset+i]==h->ranges(i),"C++/Python bucket mismatch");}}}#endifknownList[id]=h;*ret=h;returnNS_OK;}nsresultinternal_GetHistogramByName(constnsACString&name,Histogram**ret){mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(PromiseFlatCString(name).get(),&id);if(NS_FAILED(rv)){returnrv;}ProcessIDprocess=GetProcessFromName(name);rv=internal_GetHistogramByEnumId(id,ret,process);if(NS_FAILED(rv))returnrv;returnNS_OK;}#if !defined(MOZ_WIDGET_ANDROID)/** * This clones a histogram |existing| with the id |existingId| to a * new histogram with the name |newName|. * For simplicity this is limited to registered histograms. */Histogram*internal_CloneHistogram(constnsACString&newName,mozilla::Telemetry::HistogramIDexistingId,Histogram&existing){constHistogramInfo&info=gHistograms[existingId];Histogram*clone=nullptr;nsresultrv;rv=internal_HistogramGet(PromiseFlatCString(newName).get(),info.expiration(),info.histogramType,existing.declared_min(),existing.declared_max(),existing.bucket_count(),true,&clone);if(NS_FAILED(rv)){returnnullptr;}Histogram::SampleSetss;existing.SnapshotSample(&ss);clone->AddSampleSet(ss);returnclone;}ProcessIDGetProcessFromName(conststd::string&aString){nsDependentCStringstring(aString.c_str(),aString.length());returnGetProcessFromName(string);}Histogram*internal_GetSubsessionHistogram(Histogram&existing){mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(existing.histogram_name().c_str(),&id);if(NS_FAILED(rv)||gHistograms[id].keyed){returnnullptr;}staticHistogram*subsession[mozilla::Telemetry::HistogramCount]={};staticHistogram*subsessionContent[mozilla::Telemetry::HistogramCount]={};staticHistogram*subsessionGPU[mozilla::Telemetry::HistogramCount]={};staticHistogram*subsessionExtension[mozilla::Telemetry::HistogramCount]={};Histogram**cache=nullptr;ProcessIDprocess=GetProcessFromName(existing.histogram_name());switch(process){caseProcessID::Parent:cache=subsession;break;caseProcessID::Content:cache=subsessionContent;break;caseProcessID::Gpu:cache=subsessionGPU;break;caseProcessID::Extension:cache=subsessionExtension;break;default:MOZ_ASSERT_UNREACHABLE("unknown process type");returnnullptr;}if(Histogram*cached=cache[id]){returncached;}NS_NAMED_LITERAL_CSTRING(prefix,SUBSESSION_HISTOGRAM_PREFIX);nsDependentCStringexistingName(gHistograms[id].id());if(StringBeginsWith(existingName,prefix)){returnnullptr;}nsCStringsubsessionName(prefix);subsessionName.Append(existing.histogram_name().c_str());Histogram*clone=internal_CloneHistogram(subsessionName,id,existing);cache[id]=clone;returnclone;}#endifnsresultinternal_HistogramAdd(Histogram&histogram,int32_tvalue,uint32_tdataset){// Check if we are allowed to record the data.boolcanRecordDataset=CanRecordDataset(dataset,internal_CanRecordBase(),internal_CanRecordExtended());if(!canRecordDataset||!histogram.IsRecordingEnabled()){returnNS_OK;}#if !defined(MOZ_WIDGET_ANDROID)if(Histogram*subsession=internal_GetSubsessionHistogram(histogram)){subsession->Add(value);}#endif// It is safe to add to the histogram now: the subsession histogram was already// cloned from this so we won't add the sample twice.histogram.Add(value);returnNS_OK;}nsresultinternal_HistogramAdd(Histogram&histogram,int32_tvalue){uint32_tdataset=nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;// We only really care about the dataset of the histogram if we are not recording// extended telemetry. Otherwise, we always record histogram data.if(!internal_CanRecordExtended()){mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(histogram.histogram_name().c_str(),&id);if(NS_FAILED(rv)){// If we can't look up the dataset, it might be because the histogram was added// at runtime. Since we're not recording extended telemetry, bail out.returnNS_OK;}dataset=gHistograms[id].dataset;}returninternal_HistogramAdd(histogram,value,dataset);}voidinternal_HistogramClear(Histogram&aHistogram,boolonlySubsession){MOZ_ASSERT(XRE_IsParentProcess());if(!XRE_IsParentProcess()){return;}if(!onlySubsession){aHistogram.Clear();}#if !defined(MOZ_WIDGET_ANDROID)if(Histogram*subsession=internal_GetSubsessionHistogram(aHistogram)){subsession->Clear();}#endif}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: Histogram corruption helpersnamespace{voidinternal_Accumulate(mozilla::Telemetry::HistogramIDaHistogram,uint32_taSample);voidinternal_IdentifyCorruptHistograms(StatisticsRecorder::Histograms&hs){for(autoh:hs){mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(h->histogram_name().c_str(),&id);// This histogram isn't a static histogram, just ignore it.if(NS_FAILED(rv)){continue;}if(gCorruptHistograms[id]){continue;}Histogram::SampleSetss;h->SnapshotSample(&ss);Histogram::Inconsistenciescheck=h->FindCorruption(ss);boolcorrupt=(check!=Histogram::NO_INCONSISTENCIES);if(corrupt){mozilla::Telemetry::HistogramIDcorruptID=mozilla::Telemetry::HistogramCount;if(check&Histogram::RANGE_CHECKSUM_ERROR){corruptID=mozilla::Telemetry::RANGE_CHECKSUM_ERRORS;}elseif(check&Histogram::BUCKET_ORDER_ERROR){corruptID=mozilla::Telemetry::BUCKET_ORDER_ERRORS;}elseif(check&Histogram::COUNT_HIGH_ERROR){corruptID=mozilla::Telemetry::TOTAL_COUNT_HIGH_ERRORS;}elseif(check&Histogram::COUNT_LOW_ERROR){corruptID=mozilla::Telemetry::TOTAL_COUNT_LOW_ERRORS;}internal_Accumulate(corruptID,1);}gCorruptHistograms[id]=corrupt;}}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: Histogram reflection helpersnamespace{boolinternal_FillRanges(JSContext*cx,JS::Handle<JSObject*>array,Histogram*h){JS::Rooted<JS::Value>range(cx);for(size_ti=0;i<h->bucket_count();i++){range.setInt32(h->ranges(i));if(!JS_DefineElement(cx,array,i,range,JSPROP_ENUMERATE))returnfalse;}returntrue;}enumreflectStatusinternal_ReflectHistogramAndSamples(JSContext*cx,JS::Handle<JSObject*>obj,Histogram*h,constHistogram::SampleSet&ss){// We don't want to reflect corrupt histograms.if(h->FindCorruption(ss)!=Histogram::NO_INCONSISTENCIES){returnREFLECT_CORRUPT;}if(!(JS_DefineProperty(cx,obj,"min",h->declared_min(),JSPROP_ENUMERATE)&&JS_DefineProperty(cx,obj,"max",h->declared_max(),JSPROP_ENUMERATE)&&JS_DefineProperty(cx,obj,"histogram_type",h->histogram_type(),JSPROP_ENUMERATE)&&JS_DefineProperty(cx,obj,"sum",double(ss.sum()),JSPROP_ENUMERATE))){returnREFLECT_FAILURE;}constsize_tcount=h->bucket_count();JS::Rooted<JSObject*>rarray(cx,JS_NewArrayObject(cx,count));if(!rarray){returnREFLECT_FAILURE;}if(!(internal_FillRanges(cx,rarray,h)&&JS_DefineProperty(cx,obj,"ranges",rarray,JSPROP_ENUMERATE))){returnREFLECT_FAILURE;}JS::Rooted<JSObject*>counts_array(cx,JS_NewArrayObject(cx,count));if(!counts_array){returnREFLECT_FAILURE;}if(!JS_DefineProperty(cx,obj,"counts",counts_array,JSPROP_ENUMERATE)){returnREFLECT_FAILURE;}for(size_ti=0;i<count;i++){if(!JS_DefineElement(cx,counts_array,i,ss.counts(i),JSPROP_ENUMERATE)){returnREFLECT_FAILURE;}}returnREFLECT_OK;}enumreflectStatusinternal_ReflectHistogramSnapshot(JSContext*cx,JS::Handle<JSObject*>obj,Histogram*h){Histogram::SampleSetss;h->SnapshotSample(&ss);returninternal_ReflectHistogramAndSamples(cx,obj,h,ss);}boolinternal_ShouldReflectHistogram(Histogram*h){constchar*name=h->histogram_name().c_str();mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(name,&id);if(NS_FAILED(rv)){// GetHistogramEnumId generally should not fail. But a lookup// failure shouldn't prevent us from reflecting histograms into JS.//// However, these two histograms are created by Histogram itself for// tracking corruption. We have our own histograms for that, so// ignore these two.if(strcmp(name,"Histogram.InconsistentCountHigh")==0||strcmp(name,"Histogram.InconsistentCountLow")==0){returnfalse;}returntrue;}return!gCorruptHistograms[id];}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: class KeyedHistogramnamespace{classKeyedHistogram{public:KeyedHistogram(constnsACString&name,constnsACString&expiration,uint32_thistogramType,uint32_tmin,uint32_tmax,uint32_tbucketCount,uint32_tdataset);nsresultGetHistogram(constnsCString&name,Histogram**histogram,boolsubsession);Histogram*GetHistogram(constnsCString&name,boolsubsession);uint32_tGetHistogramType()const{returnmHistogramType;}nsresultGetJSKeys(JSContext*cx,JS::CallArgs&args);nsresultGetJSSnapshot(JSContext*cx,JS::Handle<JSObject*>obj,boolsubsession,boolclearSubsession);voidSetRecordingEnabled(boolaEnabled){mRecordingEnabled=aEnabled;};boolIsRecordingEnabled()const{returnmRecordingEnabled;};nsresultAdd(constnsCString&key,uint32_taSample);voidClear(boolsubsession);nsresultGetEnumId(mozilla::Telemetry::HistogramID&id);private:typedefnsBaseHashtableET<nsCStringHashKey,Histogram*>KeyedHistogramEntry;typedefAutoHashtable<KeyedHistogramEntry>KeyedHistogramMapType;KeyedHistogramMapTypemHistogramMap;#if !defined(MOZ_WIDGET_ANDROID)KeyedHistogramMapTypemSubsessionMap;#endifstaticboolReflectKeyedHistogram(KeyedHistogramEntry*entry,JSContext*cx,JS::Handle<JSObject*>obj);constnsCStringmName;constnsCStringmExpiration;constuint32_tmHistogramType;constuint32_tmMin;constuint32_tmMax;constuint32_tmBucketCount;constuint32_tmDataset;mozilla::Atomic<bool,mozilla::Relaxed>mRecordingEnabled;};KeyedHistogram::KeyedHistogram(constnsACString&name,constnsACString&expiration,uint32_thistogramType,uint32_tmin,uint32_tmax,uint32_tbucketCount,uint32_tdataset):mHistogramMap()#if !defined(MOZ_WIDGET_ANDROID),mSubsessionMap()#endif,mName(name),mExpiration(expiration),mHistogramType(histogramType),mMin(min),mMax(max),mBucketCount(bucketCount),mDataset(dataset),mRecordingEnabled(true){}nsresultKeyedHistogram::GetHistogram(constnsCString&key,Histogram**histogram,boolsubsession){#if !defined(MOZ_WIDGET_ANDROID)KeyedHistogramMapType&map=subsession?mSubsessionMap:mHistogramMap;#elseKeyedHistogramMapType&map=mHistogramMap;#endifKeyedHistogramEntry*entry=map.GetEntry(key);if(entry){*histogram=entry->mData;returnNS_OK;}nsCStringhistogramName;#if !defined(MOZ_WIDGET_ANDROID)if(subsession){histogramName.AppendLiteral(SUBSESSION_HISTOGRAM_PREFIX);}#endifhistogramName.Append(mName);histogramName.AppendLiteral(KEYED_HISTOGRAM_NAME_SEPARATOR);histogramName.Append(key);Histogram*h;nsresultrv=internal_HistogramGet(histogramName.get(),mExpiration.get(),mHistogramType,mMin,mMax,mBucketCount,true,&h);if(NS_FAILED(rv)){returnrv;}h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);*histogram=h;entry=map.PutEntry(key);if(MOZ_UNLIKELY(!entry)){returnNS_ERROR_OUT_OF_MEMORY;}entry->mData=h;returnNS_OK;}Histogram*KeyedHistogram::GetHistogram(constnsCString&key,boolsubsession){Histogram*h=nullptr;if(NS_FAILED(GetHistogram(key,&h,subsession))){returnnullptr;}returnh;}nsresultKeyedHistogram::Add(constnsCString&key,uint32_tsample){boolcanRecordDataset=CanRecordDataset(mDataset,internal_CanRecordBase(),internal_CanRecordExtended());if(!canRecordDataset||!IsRecordingEnabled()){returnNS_OK;}Histogram*histogram=GetHistogram(key,false);MOZ_ASSERT(histogram);if(!histogram){returnNS_ERROR_FAILURE;}#if !defined(MOZ_WIDGET_ANDROID)Histogram*subsession=GetHistogram(key,true);MOZ_ASSERT(subsession);if(!subsession){returnNS_ERROR_FAILURE;}#endifhistogram->Add(sample);#if !defined(MOZ_WIDGET_ANDROID)subsession->Add(sample);#endifreturnNS_OK;}voidKeyedHistogram::Clear(boolonlySubsession){MOZ_ASSERT(XRE_IsParentProcess());if(!XRE_IsParentProcess()){return;}#if !defined(MOZ_WIDGET_ANDROID)for(autoiter=mSubsessionMap.Iter();!iter.Done();iter.Next()){iter.Get()->mData->Clear();}mSubsessionMap.Clear();if(onlySubsession){return;}#endiffor(autoiter=mHistogramMap.Iter();!iter.Done();iter.Next()){iter.Get()->mData->Clear();}mHistogramMap.Clear();}nsresultKeyedHistogram::GetJSKeys(JSContext*cx,JS::CallArgs&args){JS::AutoValueVectorkeys(cx);if(!keys.reserve(mHistogramMap.Count())){returnNS_ERROR_OUT_OF_MEMORY;}for(autoiter=mHistogramMap.Iter();!iter.Done();iter.Next()){JS::RootedValuejsKey(cx);constNS_ConvertUTF8toUTF16key(iter.Get()->GetKey());jsKey.setString(JS_NewUCStringCopyN(cx,key.Data(),key.Length()));if(!keys.append(jsKey)){returnNS_ERROR_OUT_OF_MEMORY;}}JS::RootedObjectjsKeys(cx,JS_NewArrayObject(cx,keys));if(!jsKeys){returnNS_ERROR_FAILURE;}args.rval().setObject(*jsKeys);returnNS_OK;}boolKeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry*entry,JSContext*cx,JS::Handle<JSObject*>obj){JS::RootedObjecthistogramSnapshot(cx,JS_NewPlainObject(cx));if(!histogramSnapshot){returnfalse;}if(internal_ReflectHistogramSnapshot(cx,histogramSnapshot,entry->mData)!=REFLECT_OK){returnfalse;}constNS_ConvertUTF8toUTF16key(entry->GetKey());if(!JS_DefineUCProperty(cx,obj,key.Data(),key.Length(),histogramSnapshot,JSPROP_ENUMERATE)){returnfalse;}returntrue;}nsresultKeyedHistogram::GetJSSnapshot(JSContext*cx,JS::Handle<JSObject*>obj,boolsubsession,boolclearSubsession){#if !defined(MOZ_WIDGET_ANDROID)KeyedHistogramMapType&map=subsession?mSubsessionMap:mHistogramMap;#elseKeyedHistogramMapType&map=mHistogramMap;#endifif(!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram,cx,obj)){returnNS_ERROR_FAILURE;}#if !defined(MOZ_WIDGET_ANDROID)if(subsession&&clearSubsession){Clear(true);}#endifreturnNS_OK;}nsresultKeyedHistogram::GetEnumId(mozilla::Telemetry::HistogramID&id){returninternal_GetHistogramEnumId(mName.get(),&id);}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: KeyedHistogram helpersnamespace{KeyedHistogram*internal_GetKeyedHistogramById(constnsACString&name){if(!gInitDone){returnnullptr;}KeyedHistogram*keyed=nullptr;gKeyedHistograms.Get(name,&keyed);returnkeyed;}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: thread-unsafe helpers for the external interface// This is a StaticMutex rather than a plain Mutex (1) so that// it gets initialised in a thread-safe manner the first time// it is used, and (2) because it is never de-initialised, and// a normal Mutex would show up as a leak in BloatView. StaticMutex// also has the "OffTheBooks" property, so it won't show as a leak// in BloatView.staticStaticMutexgTelemetryHistogramMutex;namespace{voidinternal_SetHistogramRecordingEnabled(mozilla::Telemetry::HistogramIDaID,boolaEnabled){if(gHistograms[aID].keyed){constnsDependentCStringid(gHistograms[aID].id());KeyedHistogram*keyed=internal_GetKeyedHistogramById(id);if(keyed){keyed->SetRecordingEnabled(aEnabled);return;}}else{Histogram*h;nsresultrv=internal_GetHistogramByEnumId(aID,&h,ProcessID::Parent);if(NS_SUCCEEDED(rv)){h->SetRecordingEnabled(aEnabled);return;}}MOZ_ASSERT(false,"Telemetry::SetHistogramRecordingEnabled(...) id not found");}boolinternal_RemoteAccumulate(mozilla::Telemetry::HistogramIDaId,uint32_taSample){if(XRE_IsParentProcess()){returnfalse;}Histogram*h;nsresultrv=internal_GetHistogramByEnumId(aId,&h,ProcessID::Parent);if(NS_SUCCEEDED(rv)&&!h->IsRecordingEnabled()){returntrue;}TelemetryIPCAccumulator::AccumulateChildHistogram(aId,aSample);returntrue;}boolinternal_RemoteAccumulate(mozilla::Telemetry::HistogramIDaId,constnsCString&aKey,uint32_taSample){if(XRE_IsParentProcess()){returnfalse;}constHistogramInfo&th=gHistograms[aId];KeyedHistogram*keyed=internal_GetKeyedHistogramById(nsDependentCString(th.id()));MOZ_ASSERT(keyed);if(!keyed->IsRecordingEnabled()){returnfalse;}TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId,aKey,aSample);returntrue;}voidinternal_Accumulate(mozilla::Telemetry::HistogramIDaHistogram,uint32_taSample){if(!internal_CanRecordBase()||internal_RemoteAccumulate(aHistogram,aSample)){return;}Histogram*h;nsresultrv=internal_GetHistogramByEnumId(aHistogram,&h,ProcessID::Parent);if(NS_SUCCEEDED(rv)){internal_HistogramAdd(*h,aSample,gHistograms[aHistogram].dataset);}}voidinternal_Accumulate(mozilla::Telemetry::HistogramIDaID,constnsCString&aKey,uint32_taSample){if(!gInitDone||!internal_CanRecordBase()||internal_RemoteAccumulate(aID,aKey,aSample)){return;}constHistogramInfo&th=gHistograms[aID];KeyedHistogram*keyed=internal_GetKeyedHistogramById(nsDependentCString(th.id()));MOZ_ASSERT(keyed);keyed->Add(aKey,aSample);}voidinternal_Accumulate(Histogram&aHistogram,uint32_taSample){if(XRE_IsParentProcess()){internal_HistogramAdd(aHistogram,aSample);return;}mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(aHistogram.histogram_name().c_str(),&id);if(NS_SUCCEEDED(rv)){internal_RemoteAccumulate(id,aSample);}}voidinternal_Accumulate(KeyedHistogram&aKeyed,constnsCString&aKey,uint32_taSample){if(XRE_IsParentProcess()){aKeyed.Add(aKey,aSample);return;}mozilla::Telemetry::HistogramIDid;if(NS_SUCCEEDED(aKeyed.GetEnumId(id))){internal_RemoteAccumulate(id,aKey,aSample);}}voidinternal_AccumulateChild(ProcessIDaProcessType,mozilla::Telemetry::HistogramIDaId,uint32_taSample){if(!internal_CanRecordBase()){return;}Histogram*h;nsresultrv=internal_GetHistogramByEnumId(aId,&h,aProcessType);if(NS_SUCCEEDED(rv)){internal_HistogramAdd(*h,aSample,gHistograms[aId].dataset);}else{NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD");}}voidinternal_AccumulateChildKeyed(ProcessIDaProcessType,mozilla::Telemetry::HistogramIDaId,constnsCString&aKey,uint32_taSample){if(!gInitDone||!internal_CanRecordBase()){return;}constchar*suffix=SuffixForProcessType(aProcessType);if(!suffix){MOZ_ASSERT_UNREACHABLE("suffix should not be null");return;}constHistogramInfo&th=gHistograms[aId];nsAutoCStringid;id.Append(th.id());id.AppendASCII(suffix);KeyedHistogram*keyed=internal_GetKeyedHistogramById(id);MOZ_ASSERT(keyed);keyed->Add(aKey,aSample);}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: JSHistogram_* functions// NOTE: the functions in this section://// internal_JSHistogram_Add// internal_JSHistogram_Snapshot// internal_JSHistogram_Clear// internal_WrapAndReturnHistogram//// all run without protection from |gTelemetryHistogramMutex|. If they// held |gTelemetryHistogramMutex|, there would be the possibility of// deadlock because the JS_ calls that they make may call back into the// TelemetryHistogram interface, hence trying to re-acquire the mutex.//// This means that these functions potentially race against threads, but// that seems preferable to risking deadlock.namespace{staticconstJSClasssJSHistogramClass={"JSHistogram",/* name */JSCLASS_HAS_PRIVATE/* flags */};boolinternal_JSHistogram_Add(JSContext*cx,unsignedargc,JS::Value*vp){JSObject*obj=JS_THIS_OBJECT(cx,vp);MOZ_ASSERT(obj);if(!obj||JS_GetClass(obj)!=&sJSHistogramClass){returnfalse;}Histogram*h=static_cast<Histogram*>(JS_GetPrivate(obj));MOZ_ASSERT(h);Histogram::ClassTypetype=h->histogram_type();JS::CallArgsargs=CallArgsFromVp(argc,vp);// This function should always return |undefined| and never fail but// rather report failures using the console.args.rval().setUndefined();if(!internal_CanRecordBase()){returntrue;}uint32_tvalue=0;mozilla::Telemetry::HistogramIDid;if((type==base::CountHistogram::COUNT_HISTOGRAM)&&(args.length()==0)){// If we don't have an argument for the count histogram, assume an increment of 1.// Otherwise, make sure to run some sanity checks on the argument.value=1;}elseif(type==base::LinearHistogram::LINEAR_HISTOGRAM&&(args.length()>0)&&args[0].isString()&&NS_SUCCEEDED(internal_GetHistogramEnumId(h->histogram_name().c_str(),&id))&&gHistograms[id].histogramType==nsITelemetry::HISTOGRAM_CATEGORICAL){// For categorical histograms we allow passing a string argument that specifies the label.nsAutoJSStringlabel;if(!label.init(cx,args[0])){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Invalid string parameter"));returntrue;}// Get label id value.nsresultrv=gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(),&value);if(NS_FAILED(rv)){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Unknown label for categorical histogram"));returntrue;}}else{// All other accumulations expect one numerical argument.if(!args.length()){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Expected one argument"));returntrue;}if(!(args[0].isNumber()||args[0].isBoolean())){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Not a number"));returntrue;}if(!JS::ToUint32(cx,args[0],&value)){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Failed to convert argument"));returntrue;}}{StaticMutexAutoLocklocker(gTelemetryHistogramMutex);internal_Accumulate(*h,value);}returntrue;}boolinternal_JSHistogram_Snapshot(JSContext*cx,unsignedargc,JS::Value*vp){JS::CallArgsargs=JS::CallArgsFromVp(argc,vp);JSObject*obj=JS_THIS_OBJECT(cx,vp);if(!obj||JS_GetClass(obj)!=&sJSHistogramClass){returnfalse;}Histogram*h=static_cast<Histogram*>(JS_GetPrivate(obj));JS::Rooted<JSObject*>snapshot(cx,JS_NewPlainObject(cx));if(!snapshot)returnfalse;switch(internal_ReflectHistogramSnapshot(cx,snapshot,h)){caseREFLECT_FAILURE:returnfalse;caseREFLECT_CORRUPT:JS_ReportErrorASCII(cx,"Histogram is corrupt");returnfalse;caseREFLECT_OK:args.rval().setObject(*snapshot);returntrue;default:MOZ_CRASH("unhandled reflection status");}}boolinternal_JSHistogram_Clear(JSContext*cx,unsignedargc,JS::Value*vp){JSObject*obj=JS_THIS_OBJECT(cx,vp);if(!obj||JS_GetClass(obj)!=&sJSHistogramClass){returnfalse;}boolonlySubsession=false;JS::CallArgsargs=JS::CallArgsFromVp(argc,vp);// This function should always return |undefined| and never fail but// rather report failures using the console.args.rval().setUndefined();#if !defined(MOZ_WIDGET_ANDROID)if(args.length()>=1){if(!args[0].isBoolean()){JS_ReportErrorASCII(cx,"Not a boolean");returnfalse;}onlySubsession=JS::ToBoolean(args[0]);}#endifHistogram*h=static_cast<Histogram*>(JS_GetPrivate(obj));MOZ_ASSERT(h);if(h){internal_HistogramClear(*h,onlySubsession);}returntrue;}// NOTE: Runs without protection from |gTelemetryHistogramMutex|.// See comment at the top of this section.nsresultinternal_WrapAndReturnHistogram(Histogram*h,JSContext*cx,JS::MutableHandle<JS::Value>ret){JS::Rooted<JSObject*>obj(cx,JS_NewObject(cx,&sJSHistogramClass));if(!obj)returnNS_ERROR_FAILURE;// The 3 functions that are wrapped up here are eventually called// by the same thread that runs this function.if(!(JS_DefineFunction(cx,obj,"add",internal_JSHistogram_Add,1,0)&&JS_DefineFunction(cx,obj,"snapshot",internal_JSHistogram_Snapshot,0,0)&&JS_DefineFunction(cx,obj,"clear",internal_JSHistogram_Clear,0,0))){returnNS_ERROR_FAILURE;}JS_SetPrivate(obj,h);ret.setObject(*obj);returnNS_OK;}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PRIVATE: JSKeyedHistogram_* functions// NOTE: the functions in this section://// internal_KeyedHistogram_SnapshotImpl// internal_JSKeyedHistogram_Add// internal_JSKeyedHistogram_Keys// internal_JSKeyedHistogram_Snapshot// internal_JSKeyedHistogram_SubsessionSnapshot// internal_JSKeyedHistogram_SnapshotSubsessionAndClear// internal_JSKeyedHistogram_Clear// internal_WrapAndReturnKeyedHistogram//// Same comments as above, at the JSHistogram_* section, regarding// deadlock avoidance, apply.namespace{staticconstJSClasssJSKeyedHistogramClass={"JSKeyedHistogram",/* name */JSCLASS_HAS_PRIVATE/* flags */};boolinternal_KeyedHistogram_SnapshotImpl(JSContext*cx,unsignedargc,JS::Value*vp,boolsubsession,boolclearSubsession){JSObject*obj=JS_THIS_OBJECT(cx,vp);if(!obj||JS_GetClass(obj)!=&sJSKeyedHistogramClass){returnfalse;}KeyedHistogram*keyed=static_cast<KeyedHistogram*>(JS_GetPrivate(obj));if(!keyed){returnfalse;}JS::CallArgsargs=JS::CallArgsFromVp(argc,vp);if(args.length()==0){JS::RootedObjectsnapshot(cx,JS_NewPlainObject(cx));if(!snapshot){JS_ReportErrorASCII(cx,"Failed to create object");returnfalse;}if(!NS_SUCCEEDED(keyed->GetJSSnapshot(cx,snapshot,subsession,clearSubsession))){JS_ReportErrorASCII(cx,"Failed to reflect keyed histograms");returnfalse;}args.rval().setObject(*snapshot);returntrue;}nsAutoJSStringkey;if(!args[0].isString()||!key.init(cx,args[0])){JS_ReportErrorASCII(cx,"Not a string");returnfalse;}Histogram*h=nullptr;nsresultrv=keyed->GetHistogram(NS_ConvertUTF16toUTF8(key),&h,subsession);if(NS_FAILED(rv)){JS_ReportErrorASCII(cx,"Failed to get histogram");returnfalse;}JS::RootedObjectsnapshot(cx,JS_NewPlainObject(cx));if(!snapshot){returnfalse;}switch(internal_ReflectHistogramSnapshot(cx,snapshot,h)){caseREFLECT_FAILURE:returnfalse;caseREFLECT_CORRUPT:JS_ReportErrorASCII(cx,"Histogram is corrupt");returnfalse;caseREFLECT_OK:args.rval().setObject(*snapshot);returntrue;default:MOZ_CRASH("unhandled reflection status");}}boolinternal_JSKeyedHistogram_Add(JSContext*cx,unsignedargc,JS::Value*vp){JSObject*obj=JS_THIS_OBJECT(cx,vp);if(!obj||JS_GetClass(obj)!=&sJSKeyedHistogramClass){returnfalse;}KeyedHistogram*keyed=static_cast<KeyedHistogram*>(JS_GetPrivate(obj));if(!keyed){returnfalse;}JS::CallArgsargs=CallArgsFromVp(argc,vp);// This function should always return |undefined| and never fail but// rather report failures using the console.args.rval().setUndefined();if(args.length()<1){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Expected one argument"));returntrue;}nsAutoJSStringkey;if(!args[0].isString()||!key.init(cx,args[0])){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Not a string"));returntrue;}constuint32_ttype=keyed->GetHistogramType();// If we don't have an argument for the count histogram, assume an increment of 1.// Otherwise, make sure to run some sanity checks on the argument.uint32_tvalue=1;if((type!=nsITelemetry::HISTOGRAM_COUNT)||(args.length()==2)){if(args.length()<2){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Expected two arguments for this histogram type"));returntrue;}if(type==nsITelemetry::HISTOGRAM_CATEGORICAL&&args[1].isString()){// For categorical histograms we allow passing a string argument that specifies the label.mozilla::Telemetry::HistogramIDid;if(NS_FAILED(keyed->GetEnumId(id))){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Failed to get histogram id."));returntrue;}// Get label string.nsAutoJSStringlabel;if(!label.init(cx,args[1])){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Invalid string parameter"));returntrue;}// Get label id value.nsresultrv=gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(),&value);if(NS_FAILED(rv)){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Unknown label for categorical histogram"));returntrue;}}else{// All other accumulations expect one numerical argument.if(!(args[1].isNumber()||args[1].isBoolean())){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Not a number"));returntrue;}if(!JS::ToUint32(cx,args[1],&value)){LogToBrowserConsole(nsIScriptError::errorFlag,NS_LITERAL_STRING("Failed to convert argument"));returntrue;}}}{StaticMutexAutoLocklocker(gTelemetryHistogramMutex);internal_Accumulate(*keyed,NS_ConvertUTF16toUTF8(key),value);}returntrue;}boolinternal_JSKeyedHistogram_Keys(JSContext*cx,unsignedargc,JS::Value*vp){JSObject*obj=JS_THIS_OBJECT(cx,vp);if(!obj||JS_GetClass(obj)!=&sJSKeyedHistogramClass){returnfalse;}KeyedHistogram*keyed=static_cast<KeyedHistogram*>(JS_GetPrivate(obj));if(!keyed){returnfalse;}JS::CallArgsargs=JS::CallArgsFromVp(argc,vp);returnNS_SUCCEEDED(keyed->GetJSKeys(cx,args));}boolinternal_JSKeyedHistogram_Snapshot(JSContext*cx,unsignedargc,JS::Value*vp){returninternal_KeyedHistogram_SnapshotImpl(cx,argc,vp,false,false);}#if !defined(MOZ_WIDGET_ANDROID)boolinternal_JSKeyedHistogram_SubsessionSnapshot(JSContext*cx,unsignedargc,JS::Value*vp){returninternal_KeyedHistogram_SnapshotImpl(cx,argc,vp,true,false);}#endif#if !defined(MOZ_WIDGET_ANDROID)boolinternal_JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext*cx,unsignedargc,JS::Value*vp){JS::CallArgsargs=JS::CallArgsFromVp(argc,vp);if(args.length()!=0){JS_ReportErrorASCII(cx,"No key arguments supported for snapshotSubsessionAndClear");}returninternal_KeyedHistogram_SnapshotImpl(cx,argc,vp,true,true);}#endifboolinternal_JSKeyedHistogram_Clear(JSContext*cx,unsignedargc,JS::Value*vp){JSObject*obj=JS_THIS_OBJECT(cx,vp);if(!obj||JS_GetClass(obj)!=&sJSKeyedHistogramClass){returnfalse;}KeyedHistogram*keyed=static_cast<KeyedHistogram*>(JS_GetPrivate(obj));if(!keyed){returnfalse;}JS::CallArgsargs=JS::CallArgsFromVp(argc,vp);// This function should always return |undefined| and never fail but// rather report failures using the console.args.rval().setUndefined();#if !defined(MOZ_WIDGET_ANDROID)boolonlySubsession=false;if(args.length()>=1){if(!(args[0].isNumber()||args[0].isBoolean())){JS_ReportErrorASCII(cx,"Not a boolean");returnfalse;}onlySubsession=JS::ToBoolean(args[0]);}keyed->Clear(onlySubsession);#elsekeyed->Clear(false);#endifreturntrue;}// NOTE: Runs without protection from |gTelemetryHistogramMutex|.// See comment at the top of this section.nsresultinternal_WrapAndReturnKeyedHistogram(KeyedHistogram*h,JSContext*cx,JS::MutableHandle<JS::Value>ret){JS::Rooted<JSObject*>obj(cx,JS_NewObject(cx,&sJSKeyedHistogramClass));if(!obj)returnNS_ERROR_FAILURE;// The 6 functions that are wrapped up here are eventually called// by the same thread that runs this function.if(!(JS_DefineFunction(cx,obj,"add",internal_JSKeyedHistogram_Add,2,0)&&JS_DefineFunction(cx,obj,"snapshot",internal_JSKeyedHistogram_Snapshot,1,0)#if !defined(MOZ_WIDGET_ANDROID)&&JS_DefineFunction(cx,obj,"subsessionSnapshot",internal_JSKeyedHistogram_SubsessionSnapshot,1,0)&&JS_DefineFunction(cx,obj,"snapshotSubsessionAndClear",internal_JSKeyedHistogram_SnapshotSubsessionAndClear,0,0)#endif&&JS_DefineFunction(cx,obj,"keys",internal_JSKeyedHistogram_Keys,0,0)&&JS_DefineFunction(cx,obj,"clear",internal_JSKeyedHistogram_Clear,0,0))){returnNS_ERROR_FAILURE;}JS_SetPrivate(obj,h);ret.setObject(*obj);returnNS_OK;}}// namespace//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::// All of these functions are actually in namespace TelemetryHistogram::,// but the ::TelemetryHistogram prefix is given explicitly. This is// because it is critical to see which calls from these functions are// to another function in this interface. Mis-identifying "inwards// calls" from "calls to another function in this interface" will lead// to deadlocking and/or races. See comments at the top of the file// for further (important!) details.// Create and destroy the singleton StatisticsRecorder object.voidTelemetryHistogram::CreateStatisticsRecorder(){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);MOZ_ASSERT(!gStatisticsRecorder);gStatisticsRecorder=newbase::StatisticsRecorder();}voidTelemetryHistogram::DestroyStatisticsRecorder(){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);MOZ_ASSERT(gStatisticsRecorder);if(gStatisticsRecorder){deletegStatisticsRecorder;gStatisticsRecorder=nullptr;}}voidTelemetryHistogram::InitializeGlobalState(boolcanRecordBase,boolcanRecordExtended){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);MOZ_ASSERT(!gInitDone,"TelemetryHistogram::InitializeGlobalState ""may only be called once");gCanRecordBase=canRecordBase;gCanRecordExtended=canRecordExtended;// gHistogramMap should have been pre-sized correctly at the// declaration point further up in this file.// Populate the static histogram name->id cache.// Note that the histogram names are statically allocated.for(uint32_ti=0;i<mozilla::Telemetry::HistogramCount;i++){CharPtrEntryType*entry=gHistogramMap.PutEntry(gHistograms[i].id());entry->mData=(mozilla::Telemetry::HistogramID)i;}#ifdef DEBUGgHistogramMap.MarkImmutable();#endifmozilla::PodArrayZero(gCorruptHistograms);// Create registered keyed histogramsfor(constauto&h:gHistograms){if(!h.keyed){continue;}constnsDependentCStringid(h.id());constnsDependentCStringexpiration(h.expiration());gKeyedHistograms.Put(id,newKeyedHistogram(id,expiration,h.histogramType,h.min,h.max,h.bucketCount,h.dataset));if(XRE_IsParentProcess()){// We must create registered child keyed histograms as well or else the// same code in TelemetrySession.jsm that fails without parent keyed// histograms will fail without child keyed histograms.nsCStringcontentId(id);contentId.AppendLiteral(CONTENT_HISTOGRAM_SUFFIX);gKeyedHistograms.Put(contentId,newKeyedHistogram(id,expiration,h.histogramType,h.min,h.max,h.bucketCount,h.dataset));nsCStringgpuId(id);gpuId.AppendLiteral(GPU_HISTOGRAM_SUFFIX);gKeyedHistograms.Put(gpuId,newKeyedHistogram(id,expiration,h.histogramType,h.min,h.max,h.bucketCount,h.dataset));nsCStringextensionId(id);extensionId.AppendLiteral(EXTENSION_HISTOGRAM_SUFFIX);gKeyedHistograms.Put(extensionId,newKeyedHistogram(id,expiration,h.histogramType,h.min,h.max,h.bucketCount,h.dataset));}}// Some Telemetry histograms depend on the value of C++ constants and hardcode// their values in Histograms.json.// We add static asserts here for those values to match so that future changes// don't go unnoticed.static_assert((JS::gcreason::NUM_TELEMETRY_REASONS+1)==gHistograms[mozilla::Telemetry::GC_MINOR_REASON].bucketCount&&(JS::gcreason::NUM_TELEMETRY_REASONS+1)==gHistograms[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount&&(JS::gcreason::NUM_TELEMETRY_REASONS+1)==gHistograms[mozilla::Telemetry::GC_REASON_2].bucketCount,"NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."" If this was an intentional change, update the n_values for the ""following in Histograms.json: GC_MINOR_REASON, GC_MINOR_REASON_LONG, ""GC_REASON_2");static_assert((mozilla::StartupTimeline::MAX_EVENT_ID+1)==gHistograms[mozilla::Telemetry::STARTUP_MEASUREMENT_ERRORS].bucketCount,"MAX_EVENT_ID is assumed to be a fixed value in Histograms.json. If this"" was an intentional change, update the n_values for the following in ""Histograms.json: STARTUP_MEASUREMENT_ERRORS");gInitDone=true;}voidTelemetryHistogram::DeInitializeGlobalState(){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);gCanRecordBase=false;gCanRecordExtended=false;gHistogramMap.Clear();gKeyedHistograms.Clear();gInitDone=false;}#ifdef DEBUGboolTelemetryHistogram::GlobalStateHasBeenInitialized(){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);returngInitDone;}#endifboolTelemetryHistogram::CanRecordBase(){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);returninternal_CanRecordBase();}voidTelemetryHistogram::SetCanRecordBase(boolb){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);gCanRecordBase=b;}boolTelemetryHistogram::CanRecordExtended(){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);returninternal_CanRecordExtended();}voidTelemetryHistogram::SetCanRecordExtended(boolb){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);gCanRecordExtended=b;}voidTelemetryHistogram::InitHistogramRecordingEnabled(){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);autoprocessType=XRE_GetProcessType();for(size_ti=0;i<mozilla::ArrayLength(gHistograms);++i){constHistogramInfo&h=gHistograms[i];mozilla::Telemetry::HistogramIDid=mozilla::Telemetry::HistogramID(i);internal_SetHistogramRecordingEnabled(id,CanRecordInProcess(h.record_in_processes,processType));}for(autorecordingInitiallyDisabledID:kRecordingInitiallyDisabledIDs){internal_SetHistogramRecordingEnabled(recordingInitiallyDisabledID,false);}}voidTelemetryHistogram::SetHistogramRecordingEnabled(mozilla::Telemetry::HistogramIDaID,boolaEnabled){if(NS_WARN_IF(!internal_IsHistogramEnumId(aID))){MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");return;}constHistogramInfo&h=gHistograms[aID];if(!CanRecordInProcess(h.record_in_processes,XRE_GetProcessType())){// Don't permit record_in_process-disabled recording to be re-enabled.return;}StaticMutexAutoLocklocker(gTelemetryHistogramMutex);internal_SetHistogramRecordingEnabled(aID,aEnabled);}nsresultTelemetryHistogram::SetHistogramRecordingEnabled(constnsACString&id,boolaEnabled){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);mozilla::Telemetry::HistogramIDhId;nsresultrv=internal_GetHistogramEnumId(PromiseFlatCString(id).get(),&hId);if(NS_FAILED(rv)){returnrv;}constHistogramInfo&hi=gHistograms[hId];if(!CanRecordInProcess(hi.record_in_processes,XRE_GetProcessType())){returnNS_OK;}Histogram*h;rv=internal_GetHistogramByName(id,&h);if(NS_SUCCEEDED(rv)){h->SetRecordingEnabled(aEnabled);returnNS_OK;}KeyedHistogram*keyed=internal_GetKeyedHistogramById(id);if(keyed){keyed->SetRecordingEnabled(aEnabled);returnNS_OK;}returnNS_ERROR_FAILURE;}voidTelemetryHistogram::Accumulate(mozilla::Telemetry::HistogramIDaID,uint32_taSample){if(NS_WARN_IF(!internal_IsHistogramEnumId(aID))){MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");return;}StaticMutexAutoLocklocker(gTelemetryHistogramMutex);internal_Accumulate(aID,aSample);}voidTelemetryHistogram::Accumulate(mozilla::Telemetry::HistogramIDaID,constnsCString&aKey,uint32_taSample){if(NS_WARN_IF(!internal_IsHistogramEnumId(aID))){MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");return;}StaticMutexAutoLocklocker(gTelemetryHistogramMutex);internal_Accumulate(aID,aKey,aSample);}voidTelemetryHistogram::Accumulate(constchar*name,uint32_tsample){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);if(!internal_CanRecordBase()){return;}mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(name,&id);if(NS_FAILED(rv)){return;}internal_Accumulate(id,sample);}voidTelemetryHistogram::Accumulate(constchar*name,constnsCString&key,uint32_tsample){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);if(!internal_CanRecordBase()){return;}mozilla::Telemetry::HistogramIDid;nsresultrv=internal_GetHistogramEnumId(name,&id);if(NS_SUCCEEDED(rv)){internal_Accumulate(id,key,sample);}}voidTelemetryHistogram::AccumulateCategorical(mozilla::Telemetry::HistogramIDaId,constnsCString&label){if(NS_WARN_IF(!internal_IsHistogramEnumId(aId))){MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");return;}StaticMutexAutoLocklocker(gTelemetryHistogramMutex);if(!internal_CanRecordBase()){return;}uint32_tlabelId=0;if(NS_FAILED(gHistograms[aId].label_id(label.get(),&labelId))){return;}internal_Accumulate(aId,labelId);}voidTelemetryHistogram::AccumulateChild(ProcessIDaProcessType,constnsTArray<Accumulation>&aAccumulations){MOZ_ASSERT(XRE_IsParentProcess());StaticMutexAutoLocklocker(gTelemetryHistogramMutex);if(!internal_CanRecordBase()){return;}for(uint32_ti=0;i<aAccumulations.Length();++i){if(NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations[i].mId))){MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");continue;}internal_AccumulateChild(aProcessType,aAccumulations[i].mId,aAccumulations[i].mSample);}}voidTelemetryHistogram::AccumulateChildKeyed(ProcessIDaProcessType,constnsTArray<KeyedAccumulation>&aAccumulations){MOZ_ASSERT(XRE_IsParentProcess());StaticMutexAutoLocklocker(gTelemetryHistogramMutex);if(!internal_CanRecordBase()){return;}for(uint32_ti=0;i<aAccumulations.Length();++i){if(NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations[i].mId))){MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");continue;}internal_AccumulateChildKeyed(aProcessType,aAccumulations[i].mId,aAccumulations[i].mKey,aAccumulations[i].mSample);}}nsresultTelemetryHistogram::GetHistogramById(constnsACString&name,JSContext*cx,JS::MutableHandle<JS::Value>ret){Histogram*h=nullptr;{StaticMutexAutoLocklocker(gTelemetryHistogramMutex);nsresultrv=internal_GetHistogramByName(name,&h);if(NS_FAILED(rv))returnrv;}// Runs without protection from |gTelemetryHistogramMutex|returninternal_WrapAndReturnHistogram(h,cx,ret);}nsresultTelemetryHistogram::GetKeyedHistogramById(constnsACString&name,JSContext*cx,JS::MutableHandle<JS::Value>ret){KeyedHistogram*keyed=nullptr;{StaticMutexAutoLocklocker(gTelemetryHistogramMutex);if(!gKeyedHistograms.Get(name,&keyed)){returnNS_ERROR_FAILURE;}}// Runs without protection from |gTelemetryHistogramMutex|returninternal_WrapAndReturnKeyedHistogram(keyed,cx,ret);}constchar*TelemetryHistogram::GetHistogramName(mozilla::Telemetry::HistogramIDid){if(NS_WARN_IF(!internal_IsHistogramEnumId(id))){MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");returnnullptr;}StaticMutexAutoLocklocker(gTelemetryHistogramMutex);constHistogramInfo&h=gHistograms[id];returnh.id();}nsresultTelemetryHistogram::CreateHistogramSnapshots(JSContext*cx,JS::MutableHandle<JS::Value>ret,boolsubsession,boolclearSubsession){// Runs without protection from |gTelemetryHistogramMutex|JS::Rooted<JSObject*>root_obj(cx,JS_NewPlainObject(cx));if(!root_obj)returnNS_ERROR_FAILURE;ret.setObject(*root_obj);// Include the GPU process in histogram snapshots only if we actually tried// to launch a process for it.boolincludeGPUProcess=false;if(autogpm=mozilla::gfx::GPUProcessManager::Get()){includeGPUProcess=gpm->AttemptedGPUProcess();}// Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have// been created, so that their values are snapshotted.autoprocessType=XRE_GetProcessType();for(size_ti=0;i<mozilla::Telemetry::HistogramCount;++i){constHistogramInfo&hi=gHistograms[i];if(hi.keyed||!CanRecordInProcess(hi.record_in_processes,processType)){continue;}constuint32_ttype=hi.histogramType;if(type==nsITelemetry::HISTOGRAM_FLAG||type==nsITelemetry::HISTOGRAM_COUNT){Histogram*h;mozilla::DebugOnly<nsresult>rv;mozilla::Telemetry::HistogramIDid=mozilla::Telemetry::HistogramID(i);for(uint32_tprocess=0;process<static_cast<uint32_t>(ProcessID::Count);++process){if((ProcessID(process)==ProcessID::Gpu)&&!includeGPUProcess){continue;}rv=internal_GetHistogramByEnumId(id,&h,ProcessID(process));MOZ_ASSERT(NS_SUCCEEDED(rv));}}}StatisticsRecorder::Histogramshs;StatisticsRecorder::GetHistograms(&hs);// We identify corrupt histograms first, rather than interspersing it// in the loop below, to ensure that our corruption statistics don't// depend on histogram enumeration order.//// Of course, we hope that all of these corruption-statistics// histograms are not themselves corrupt...internal_IdentifyCorruptHistograms(hs);// OK, now we can actually reflect things.JS::Rooted<JSObject*>hobj(cx);for(size_ti=0;i<mozilla::Telemetry::HistogramCount;++i){constHistogramInfo&hi=gHistograms[i];if(hi.keyed){continue;}Histogram*h=nullptr;mozilla::Telemetry::HistogramIDid=mozilla::Telemetry::HistogramID(i);for(uint32_tprocess=0;process<static_cast<uint32_t>(ProcessID::Count);++process){if(!CanRecordInProcess(hi.record_in_processes,ProcessID(process))||((ProcessID(process)==ProcessID::Gpu)&&!includeGPUProcess)){continue;}nsresultrv=internal_GetHistogramByEnumId(id,&h,ProcessID(process));if(NS_WARN_IF(NS_FAILED(rv))||!internal_ShouldReflectHistogram(h)||internal_IsEmpty(h)||internal_IsExpired(h)){continue;}Histogram*original=h;#if !defined(MOZ_WIDGET_ANDROID)if(subsession){h=internal_GetSubsessionHistogram(*h);if(!h){continue;}}#endifhobj=JS_NewPlainObject(cx);if(!hobj){returnNS_ERROR_FAILURE;}switch(internal_ReflectHistogramSnapshot(cx,hobj,h)){caseREFLECT_CORRUPT:// We can still hit this case even if ShouldReflectHistograms// returns true. The histogram lies outside of our control// somehow; just skip it.continue;caseREFLECT_FAILURE:returnNS_ERROR_FAILURE;caseREFLECT_OK:if(!JS_DefineProperty(cx,root_obj,original->histogram_name().c_str(),hobj,JSPROP_ENUMERATE)){returnNS_ERROR_FAILURE;}}#if !defined(MOZ_WIDGET_ANDROID)if(subsession&&clearSubsession){h->Clear();}#endif}}returnNS_OK;}nsresultTelemetryHistogram::RegisteredHistograms(uint32_taDataset,uint32_t*aCount,char***aHistograms){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);returninternal_GetRegisteredHistogramIds(false,aDataset,aCount,aHistograms);}nsresultTelemetryHistogram::RegisteredKeyedHistograms(uint32_taDataset,uint32_t*aCount,char***aHistograms){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);returninternal_GetRegisteredHistogramIds(true,aDataset,aCount,aHistograms);}nsresultTelemetryHistogram::GetKeyedHistogramSnapshots(JSContext*cx,JS::MutableHandle<JS::Value>ret){// Runs without protection from |gTelemetryHistogramMutex|JS::Rooted<JSObject*>obj(cx,JS_NewPlainObject(cx));if(!obj){returnNS_ERROR_FAILURE;}for(autoiter=gKeyedHistograms.Iter();!iter.Done();iter.Next()){JS::RootedObjectsnapshot(cx,JS_NewPlainObject(cx));if(!snapshot){returnNS_ERROR_FAILURE;}if(!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx,snapshot,false,false))){returnNS_ERROR_FAILURE;}if(!JS_DefineProperty(cx,obj,PromiseFlatCString(iter.Key()).get(),snapshot,JSPROP_ENUMERATE)){returnNS_ERROR_FAILURE;}}ret.setObject(*obj);returnNS_OK;}size_tTelemetryHistogram::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOfaMallocSizeOf){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);returngHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf);}size_tTelemetryHistogram::GetHistogramSizesofIncludingThis(mozilla::MallocSizeOfaMallocSizeOf){StaticMutexAutoLocklocker(gTelemetryHistogramMutex);StatisticsRecorder::Histogramshs;StatisticsRecorder::GetHistograms(&hs);size_tn=0;for(autoh:hs){n+=h->SizeOfIncludingThis(aMallocSizeOf);}returnn;}